<template>
  <!-- <div id="container">
  </div> -->
</template>

<script setup>
import * as THREE from 'three'
import { onMounted } from 'vue';
// 引入轨道控制器扩展库OrbitControls.js
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
//引入性能监视器stats.js
import Stats from 'three/addons/libs/stats.module.js';
// 引入dat.gui.js的一个类GUI
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
// 引入渲染器通道RenderPass
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';

// 引入OutlinePass通道
import { OutlinePass } from 'three/addons/postprocessing/OutlinePass.js';

// 实例化一个gui对象
const gui = new GUI();
//改变交互界面style属性
gui.domElement.style.right = '0px';
gui.domElement.style.width = '300px';

//创建一个对象，对象属性的值可以被GUI库创建的交互界面改变
// const obj = {
//   x: 30,
// };
// // gui增加交互界面，用来改变obj对应属性
// gui.add(obj, 'x', 0, 100);
// setInterval(function () {
//   console.log('x', obj.x);
// }, 1000)

const scene = new THREE.Scene()

//创建一个长方体几何对象Geometry
// //BoxGeometry：长方体
// const geometry = new THREE.BoxGeometry(50, 50, 50);
//创建一个空的几何体对象
const geometry = new THREE.BoxGeometry(100, 100, 100);

const material = new THREE.MeshBasicMaterial({
  color: 0x0000ff, //材质颜色
  // wireframe: true //线条模式渲染mesh对应的三角形数据
  // side: THREE.FrontSide, //默认只有正面可见
});
const mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
//  本地(局部)坐标和世界坐标
mesh.position.x = 50
const group = new THREE.Group()
//     改变子对象的.position，子对象在3D空间中的坐标会发生改变。

// 改变父对象的.position，子对象在3D空间中的位置也会跟着变化，也就是说父对象.position和子对象.position叠加才是才是子对象的.position。

// 任何一个模型的本地坐标(局部坐标)就是模型的.position属性。

// 一个模型的世界坐标，说的是，模型自身.position和所有父对象.position累加的坐标
group.position.x = 50
group.add(mesh)
scene.add(group)

//   .getWorldPosition()获取世界坐标

// mesh.getWorldPosition(Vector3)读取一个模型的世界坐标，并把读取结果存储到参数Vector3中。
// 声明一个三维向量用来表示某个坐标
const worldPosition = new THREE.Vector3();
// 获取mesh的世界坐标，你会发现mesh的世界坐标受到父对象group的.position影响
mesh.getWorldPosition(worldPosition);
console.log('世界坐标', worldPosition);
console.log('本地坐标', mesh.position);

// 给子对象添加一个局部坐标系

// mesh.add(坐标系)给mesh添加一个局部坐标系。

//可视化mesh的局部坐标系
const meshAxesHelper1 = new THREE.AxesHelper(50);
mesh.add(meshAxesHelper1);

// 查看父类Object3D的移除方法.remove()
// .remove()方法使用

//   .add()方法是给父对象添加一个子对象，.remove()方法是删除父对象中的一个子对象。

// // 删除父对象group的子对象网格模型mesh1
// group.remove(mesh1);

// scene.remove(ambient);//移除场景中环境光
// scene.remove(model);//移除场景中模型对象

// 通过.remove()方法删除父对象的子对象之后，可以通过浏览器控制台查看.children()属性的变化。

// console.log('查看group的子对象', group.children);

// #
// 一次移除多个子对象

// group.remove(mesh1, mesh2);

// 开发web3d项目，有时候需要临时隐藏一个模型，或者一个模型处于隐藏状态，需要重新恢复显示。
// #
// 模型属性.visible

// 模型对象的父类Object3D封装了一个属性.visible，通过该属性可以隐藏或显示一个模型。

// mesh.visible = false;// 隐藏一个网格模型，visible的默认值是true
// group.visible = false;// 隐藏一个包含多个模型的组对象group

// mesh.visible = true;// 使网格模型mesh处于显示状态

// #
// 材质属性.visible

// 材质对象的父类Material封装了一个.visible属性，通过该属性可以控制是否隐藏该材质对应的模型对象。

// // 隐藏网格模型mesh，visible的默认值是true
// mesh.material.visible = false;
// // 注意如果mesh2和mesh的.material属性指向同一个材质，mesh2也会跟着mesh隐藏

// 注意: 如果多个模型引用了同一个材质，如果该材质.visible设置为false，意味着隐藏绑定该材质的所有模型。


//点光源：两个参数分别表示光源颜色和光照强度
// 参数1：0xffffff是纯白光,表示光源颜色
// 参数2：1.0,表示光照强度，可以根据需要调整
const pointLight = new THREE.PointLight(0xffffff, 1.0);
pointLight.decay = 0.0;//设置光源不随距离衰减
//点光源位置
pointLight.position.set(200, 200, 300);//点光源放在x轴上
scene.add(pointLight); //点光源添加到场景中

//环境光:没有特定方向，整体改变场景的光照明暗
const ambient = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambient);

// 平行光
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
// 设置光源的方向：通过光源position属性和目标指向对象的position属性计算
directionalLight.position.set(-80, -100, -50);
// 方向光指向对象网格模型mesh，可以不设置，默认的位置是0,0,0
directionalLight.target = mesh;
scene.add(directionalLight);
// DirectionalLightHelper：可视化平行光
const dirLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5, 0xff0000);
scene.add(dirLightHelper);



// 点光源辅助可视化观察
const pointLightHelper = new THREE.PointLightHelper(pointLight, 10);
scene.add(pointLightHelper);


// AxesHelper：辅助观察的坐标系
const axesHelper = new THREE.AxesHelper(300);
scene.add(axesHelper);

const obj2 = {
  bool: false,
};

const width1 = 800; //宽度
const height1 = 500; //高度
// 实例化一个透视投影相机对象
// 30:视场角度, width / height:Canvas画布宽高比, 1:近裁截面, 3000：远裁截面
// // 超出视锥体远裁界面的范围的会被剪裁掉，不渲染  可以调整far参数适配 所以要看全部就把远裁面拉高10000
const camera = new THREE.PerspectiveCamera(30, width1 / height1, 1, 10000);

//相机在Three.js三维坐标系中的位置
// 根据需要设置相机位置具体值
camera.position.set(500, 500, 500);
// camera.lookAt(mesh.position);//指向mesh对应的位置
// // 改变相机观察目标点
camera.lookAt(250, 0, 250);

// width和height用来设置Three.js输出的Canvas画布尺寸(像素px)
// const width = 800; //宽度
// const height = 500; //高度
const width = window.innerWidth; //宽度
const height = window.innerHeight; //高度
// 创建渲染器对象
// 设置渲染器锯齿属性.antialias的值可以直接在参数中设置，也可通过渲染器对象属性设置
const renderer = new THREE.WebGLRenderer({
  antialias: true,
});
// 或者直接设置参数  renderer.antialias = true


// 不同硬件设备的屏幕的设备像素比window.devicePixelRatio值可能不同
console.log('查看当前屏幕设备像素比', window.devicePixelRatio);

// 如果你遇到你的canvas画布输出模糊问题，注意设置renderer.setPixelRatio(window.devicePixelRatio)。
// 注意：注意你的硬件设备设备像素比window.devicePixelRatio刚好是1，那么是否执行.setPixelRatio()不会有明显差异，不过为了适应不同的硬件设备屏幕，通常需要执行该方法。
// 获取你屏幕对应的设备像素比.devicePixelRatio告诉threejs,以免渲染模糊问题
renderer.setPixelRatio(window.devicePixelRatio);

renderer.setClearColor(0x444444, 1); //设置背景颜色

renderer.setSize(width, height); //设置three.js渲染区域的尺寸(像素px)
renderer.render(scene, camera); //执行渲染操作
onMounted(() => {
  // document.getElementById('container').appendChild(renderer.domElement);
  document.body.appendChild(renderer.domElement);
})

// 设置相机控件轨道控制器OrbitControls
const controls = new OrbitControls(camera, renderer.domElement);

// // 改变相机观察目标点 camera.lookAt(250, 0, 250);
// 意相机控件OrbitControls会影响lookAt设置，注意手动设置OrbitControls的目标参数
controls.target.set(250, 0, 250)
controls.update()


// 如果OrbitControls改变了相机参数，重新调用渲染器渲染三维场景
controls.addEventListener('change', function () {
  // 浏览器控制台查看相机位置变化
  // console.log('camera.position', camera.position);
  // 渲染循环和相机控件OrbitControls

  // 设置了渲染循环, 相机控件OrbitControls就不用再通过事件change执行renderer.render(scene, camera);，毕竟渲染循环一直在执行renderer.render(scene, camera);。
  // renderer.render(scene, camera); //执行渲染操作
});//监听鼠标、键盘事件

const clock = new THREE.Clock();
//创建stats对象
const stats = new Stats();
//stats.domElement:web页面上输出计算结果,一个div元素，
document.body.appendChild(stats.domElement);

// 创建后处理对象EffectComposer，WebGL渲染器作为参数
const composer = new EffectComposer(renderer);
// 创建OutlinePass通道

// OutlinePass第一个参数v2的尺寸和canvas画布保持一致
const v2 = new THREE.Vector2(window.innerWidth, window.innerHeight);
// const v2 = new THREE.Vector2(800, 600);
const outlinePass = new OutlinePass(v2, scene, camera);
// OutlinePass属性.selectedObjects
// 创建一个渲染器通道，场景和相机作为参数
const renderPass = new RenderPass(scene, camera);

// three.js场景中有多个模型的话，你希望给哪个模型对象设置发光描边效果，就可以通过OutlinePass的选择对象属性.selectedObjects设置。

// 一个模型对象
outlinePass.selectedObjects = [mesh];
//模型描边颜色，默认白色         
outlinePass.visibleEdgeColor.set(0xffff00);
//高亮发光描边厚度
outlinePass.edgeThickness = 4;
//高亮描边发光强度
outlinePass.edgeStrength = 6;
//模型闪烁频率控制，默认0不闪烁
outlinePass.pulsePeriod = 2;

// #
// 设置OutlinePass通道

// 最后把创建好的OutlinePass通道添加到后处理composer中。

// 设置OutlinePass通道
composer.addPass(outlinePass);

// 给EffectComposer添加一个渲染器通道RenderPass。

// 设置renderPass通道
composer.addPass(renderPass);


function render() {
  // const spt = clock.getDelta() * 1000;//毫秒
  // console.log('两帧渲染时间间隔(毫秒)', spt);
  // console.log('帧率FPS', 1000 / spt);
  stats.update();
  composer.render();
  // renderer.render(scene, camera); //执行渲染操作

  // mesh.rotateY(0.01);// mesh旋转动画
  // // 同步mesh2和mesh的姿态角度一样，不管mesh姿态角度怎么变化，mesh2始终保持同步
  // mesh2.rotation.copy(mesh.rotation);

  // mesh.rotation.y += 0.01;
  // mesh.rotation.y += 0.01;//每次绕y轴旋转0.01弧度
  // mesh.rotateY(0.01); // GUI进行单选框控制是否旋转
  // mesh.rotateY(0.01);//每次绕y轴旋转0.01弧度
  requestAnimationFrame(render);//请求再次执行渲染函数render，渲染下一帧
}
render();
// onresize 事件会在窗口被调整大小时发生
window.onresize = function () {
  // 重置渲染器输出画布canvas尺寸
  renderer.setSize(window.innerWidth, window.innerHeight);
  // 全屏情况下：设置观察范围长宽比aspect为窗口宽高比
  camera.aspect = window.innerWidth / window.innerHeight;
  // 渲染器执行render方法的时候会读取相机对象的投影矩阵属性projectionMatrix
  // 但是不会每渲染一帧，就通过相机的属性计算投影矩阵(节约计算资源)
  // 如果相机的一些属性发生了变化，需要执行updateProjectionMatrix ()方法更新相机的投影矩阵
  camera.updateProjectionMatrix();
};



</script>
<style scoped>
#container {
  width: 500px;
  height: 500px;
  margin: 100px auto;
  background: gray;
}
</style>